package com.interviews.threadlocal;

/**
 * 多个线程同时去执行，但是这些线程同时去访问这个 ThreadLocal 并且能利用 ThreadLocal 拿到只属于自己的独享对象。
 * 这样的话，就无需任何额外的措施，保证了线程安全，因为每个线程是独享 user 对象的
 *
 * ThreadLocal 的两个典型的使用场景。
 *
 * 场景1，ThreadLocal 用作保存每个线程独享的对象，为每个线程都创建一个副本，每个线程都只能修改自己所拥有的副本,
 * 而不会影响其他线程的副本，这样就让原本在并发情况下，线程不安全的情况变成了线程安全的情况。
 *
 * 场景2，ThreadLocal 用作每个线程内需要独立保存信息的场景，供其他方法更方便得获取该信息，每个线程获取到的信息
 * 都可能是不一样的，前面执行的方法设置了信息后，后续方法可以通过 ThreadLocal 直接获取到，避免了传参。
 *
 * ThreadLocal 是不是用来解决共享资源的多线程访问的?
 *
 * 不是，ThreadLocal 并不是用来解决共享资源问题的。虽然 ThreadLocal 确实可以用于解决多线程情况下的线程安全
 * 问题，但其资源并不是共享的，而是每个线程独享的。
 *
 * ThreadLocal 解决线程安全问题的时候，相比于使用“锁”而言，换了一个思路，把资源变成了各线程独享的资源，非常巧
 * 妙地避免了同步操作。具体而言，它可以在 initialValue 中 new 出自己线程独享的资源，而多个线程之间，它们所访
 * 问的对象本身是不共享的，自然就不存在任何并发问题。这是 ThreadLocal 解决并发问题的最主要思路。
 *
 * @author qian
 * @version 1.0
 * @date 2022/3/3 16:59
 */
public class ThreadLocalDemo07 {

    public static void main(String[] args) {
        new Service1().service1();
    }
}

class Service1 {

    public void service1() {
        User user = new User("小明");
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}

class User {
    private final String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class UserContextHolder {

    public static ThreadLocal<User> holder = new ThreadLocal<>();
}

class Service2 {
    public void service2() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service2拿到用户名：" + user.getName());
        new Service3().service3();
    }
}
class Service3 {
    public void service3() {
        User user = UserContextHolder.holder.get();
        /*
          内存泄漏-为何每次用完ThreadLocal都要调用remove()?
               每一个Thread都有一个ThreadLocal.ThreadLocalMap这样的类型变量，该变量的名字叫做threadLocals。
            线程在访问了ThreadLocal之后，都会在它的ThreadLocalMap里面的Entry中去维护该ThreadLocal变量与具体实
            例的映射。

               我们可能会在业务代码中执行了ThreadLocal instance = null操作，想清理掉这个ThreadLocal实例，但
            是假设我们在ThreadLocalMap的Entry中强引用了ThreadLocal实例，那么，虽然在业务代码中把ThreadLocal
            实例置为null,但是在Thread类中、依然有这个引用链的存在。

            GC 在垃圾回收的时候会进行可达性分析，它会发现这个 ThreadLocal 对象依然是可达的，所以对于这个
            ThreadLocal 对象不会进行垃圾回收，这样的话就造成了内存泄漏的情况。


               Entry 是 extends WeakReference。弱引用的特点是，如果这个对象只被弱引用关联，而没有任何强引用关联，
            那么这个对象就可以被回收，所以弱引用不会阻止 GC。因此，这个弱引用的机制就避免了 ThreadLocal 的内存泄露问题。
            这就是为什么 Entry 的 key 要使用弱引用的原因。

            使用完了 ThreadLocal 之后，我们应该手动去调用它的 remove 方法，目的是防止内存泄漏的发生。
         */
        UserContextHolder.holder.remove();
        System.out.println("Service3拿到用户名：" + user.getName());
    }
}
